home *** CD-ROM | disk | FTP | other *** search
/ Language/OS - Multiplatform Resource Library / LANGUAGE OS.iso / lisp / elk-2_0.lha / elk-2.0 / doc / ex / ex.ms next >
Text File  |  1992-10-27  |  62KB  |  1,817 lines

  1. .RP
  2. .fp 5 H
  3. .fp 7 C
  4. .de S
  5. .\".ps 12
  6. .ft 7
  7. .if \\n(.$=1 \&\\$1
  8. .if \\n(.$>1 \&\\$1\c
  9. .ft
  10. .\".ps
  11. .if \\n(.$>1 \&\\$2
  12. ..
  13. .de PG
  14. .DS
  15. .ft 5
  16. ..
  17. .de SC
  18. .DS
  19. .ft 7
  20. .\".ps 12
  21. ..
  22. .de PE
  23. .ft
  24. .DE
  25. .LP
  26. ..
  27. .de C
  28. .br
  29. .ne 3c
  30. .SH
  31. \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
  32. .XS
  33. \\$1 \\$2 \\$3 \\$4 \\$5 \\$6 \\$7 \\$8 \\$9
  34. .XE
  35. ..
  36. .TL
  37. Interfacing Scheme to the ``Real World''
  38. .br
  39. \(em a non-trivial example \(em
  40. .AU
  41. Oliver Laumann
  42. .AB
  43. This memo serves as a guide for implementors who want to interface the
  44. Scheme [1] interpreter described in [2,3,4] to external applications
  45. or libraries.
  46. This is especially important in cases where
  47. Scheme is used as the \f2extension language\fP of a larger
  48. system.
  49. The steps involved in such an integration are illustrated
  50. in detail by a hypothetical but non-trivial example.
  51. .AE
  52. .C Introduction
  53. .PP
  54. This paper considers the situation where Scheme is not used in a
  55. ``stand-alone'' way, but as an integral part of a larger environment,
  56. for instance, as the extension language of another application.
  57. In such cases, the objects and functions provided by the environment
  58. must be accessible from within Scheme.
  59. .PP
  60. Providing interfaces to the ``non-Scheme world'' usually involves
  61. the creation of new abstract data types which hide irrelevant details
  62. of the data types maintained by the environment and ``protect'' access to
  63. them.
  64. In addition, Scheme functions must finally call low-level functions
  65. provided by the environment.
  66. Since Scheme does not directly support
  67. the definition of new data types and interfaces to foreign-language
  68. functions, the implementor must provide the necessary ``glue''
  69. between the Scheme interpreter and the environment; this ``glue''
  70. must be written in a (comparatively) low-level language like ``C''
  71. or ``C++'' [5].
  72. .PP
  73. Fortunately this is not as difficult as it may sound, since such
  74. ``interface code'' usually has a high degree of reusability
  75. (which is exactly the reason why this memo was actually written).
  76. However, an important decision facing the interface implementor is
  77. where to draw the line between the low-level part of an interface,
  78. that is, the part which is written in (say) ``C'' and therefore
  79. ``cast in stone'' from the Scheme programmer's point of view,
  80. and the part that is written in Scheme.
  81. Taking this decision partly involves a tradeoff between performance
  82. on the one hand and flexibility and customizability on the other hand,
  83. and partly it depends on whether something can be done easier (or less
  84. costly) in a language like ``C'' or in Scheme.
  85. .PP
  86. The rest of this paper presents an example for such an interface
  87. in a detailed way.
  88. After starting with a few basic definitions,
  89. the code is refined and extended in a number of steps, and possible
  90. problems and improvements as well as the conventions used
  91. (both coding and naming conventions) are pointed out.
  92. The example is non-trivial in the sense that
  93. problems that typically arise when such an interface is developed
  94. are taken into account.
  95. The example can be viewed as a prototype or a model for similar
  96. integrations.
  97. In fact, many Scheme data types have been defined by taking existing
  98. code and only modifying the relevant parts.
  99. .PP
  100. On the other hand, the example is hypothetical; the ``graphics library''
  101. or ``window system'' being discussed does not exist in reality.
  102. The advantage of choosing a hypothetical environment is that many
  103. details that would distract or are irrelevant for the example
  104. can be omitted.
  105. .QP
  106. \s-1\f3Typographic Conventions:\fP Examples written in ``C'',
  107. ``C'' identifiers, names of ``C'' types etc. are typeset
  108. in a sans-serif typeface; thus, \f5Object\fP is the name of a type,
  109. while ``object'' is the generic term.
  110. Scheme code and Scheme symbols are typeset using a fixed-width
  111. typeface; thus,
  112. .S equal?
  113. is the name of a Scheme predicate, not a question.\s0
  114. .sp .5
  115. .C The Hypothetical Window System
  116. .PP
  117. Suppose that you are writing an application on a graphics workstation
  118. under the control of a window system (or graphics system).
  119. The window system provides a ``C''-library of functions that can
  120. be used to place individual windows on the graphics display, to
  121. query and modify the attributes (such as position and size)
  122. of a window, to destroy a window, and others.
  123. To begin with, let us assume that the window system library exports
  124. the functions
  125. .PG
  126. Window Window_Create (int x, int y, int width, int height)
  127. .PE
  128. and
  129. .PG
  130. void Window_Destroy (Window w)
  131. .PE
  132. .PP
  133. \f5Window_Create()\fP places a window with the given dimensions on the screen;
  134. it returns a \f2handle\fP or \f2ID\fP for the newly-created
  135. window that is used to reference the window in later calls to the library.
  136. The type \f5Window\fP might be represented as a \f5long int\fP.
  137. \f5Window_Destroy()\fP is the inverse operation; it instructs the window
  138. system to free the resources associated with the window and remove it
  139. from the screen.
  140. .PP
  141. Our goal is to be able to create and maintain windows from within
  142. Scheme, this apparently involves the definition of a data type
  143. .S window
  144. and a number of \f2primitive procedures\fP that operate on
  145. objects of type
  146. .S window .
  147. Ideally, we want to be able to write code like
  148. .SC
  149. (define width 400)
  150. (define w
  151.   (make-window 0 0 width (* 2 width)))
  152. .sp .5
  153. (window? w)  ; returns #t
  154. .sp .5
  155. ;; [do something with the window]
  156. .sp .5
  157. (destroy-window w)
  158. .PE
  159. .PP
  160. The new type
  161. .S window
  162. should be indistinguishable from other
  163. ``first-class'' types such as
  164. .S integer
  165. or
  166. .S vector ;
  167. for instance, the usual predicates should also work on objects
  168. of this type.
  169. .C The Anatomy of a Scheme Type
  170. .PP
  171. The Scheme interpreter maintains a \f2generic\fP type called \f5Object\fP.
  172. Thus, all variables that can hold a Scheme object must be declared
  173. as \f5Object\fP.
  174. An \f5Object\fP is basically a 32-bit value (the actual size may
  175. vary between different target machines) which is composed of
  176. a \f2tag\fP part and a \f2pointer\fP part. \**
  177. .FS
  178. Some special types of objects, like small numbers, characters, or
  179. booleans are directly stored in the \f2pointer\fP component of an
  180. \f5Object\fP.
  181. The definition of this kind of objects is not considered in this memo.
  182. .FE
  183. The \f2tag\fP part indicates the type of the object, and the remaining
  184. bits hold the actual memory address of the object (that is, they
  185. point into the heap).
  186. The macros \f5TYPE()\fP and \f5POINTER()\fP are provided to extract
  187. the fields of an \f5Object\fP.
  188. Since objects are usually represented as a ``C''-structure, and since
  189. the raw memory address of an object is seldom of any use, the result
  190. of \f5POINTER()\fP is usually casted like this:
  191. .PG
  192. #define VECTOR(obj) ((struct S_Vector *)POINTER(obj))
  193. .PE
  194. where \f5S_Vector\fP is the actual representation of a
  195. .S vector .
  196. Applying \f5VECTOR\fP to an \f5Object\fP obviously only makes sense
  197. when it is known or has been verified that the object is really
  198. a vector:
  199. .PG
  200.  ...
  201. if (TYPE(x) == T_Vector)
  202.     num_items = VECTOR(x)\->size;
  203.  ...
  204. .PE
  205. .PP
  206. This enables us to begin with the definition of the new type
  207. .S window ;
  208. the representation of an object of this type and the macro to access
  209. the individual components can be defined as
  210. .PG
  211. struct S_Window {
  212.     Window handle;
  213. };
  214. #define WINDOW(obj) ((struct S_Window *)POINTER(obj))
  215. .PE
  216. where \f5handle\fP holds the value returned by a call to \f5Window_Create()\fP.
  217. It is not really necessary to use a \f2struct\fP here, but it seems likely that
  218. the type is going to be extended by additional components in the future.
  219. .QP
  220. \s-1\f3Naming Convention:\fP Type structures are usually given names
  221. starting with \f5S_\fP followed by the capitalized type name.
  222. The name of the access macro is obtained by capitalizing all letters of
  223. the type name.
  224. The variable holding the number identifying the
  225. type itself is written as \f5T_\fP followed
  226. by the type name capitalized.
  227. .sp .5
  228. Although these conventions are not enforced, they are recommended to
  229. ease understanding of the code by other people.\s0
  230. .C More About Types
  231. .PP
  232. The Scheme interpreter is instructed to register a new type by means
  233. of the function \f5Define_Type()\fP.
  234. It returns the ID of the new type, that is, the value that is
  235. going to be returned by \f5TYPE(obj)\fP for all objects of that type.
  236. If the first argument is zero, a new type ID is allocated by the
  237. Scheme interpreter.
  238. If, for some reason, the type ID must be a constant chosen by
  239. the implementor, that constant is passed to \f5Define_Type()\fP
  240. instead of the zero argument.
  241. However, this is discouraged since a programmer-chosen type ID
  242. is likely to conflict with types defined by other programmers
  243. (\f5Define_Type\fP fails in this case).
  244. That is, the typical usage is
  245. .PG
  246. T_Window = Define_Type (0, ...);
  247. .PE
  248. .PP
  249. The second argument is the name of the newly defined type as a
  250. null-terminated string (such as \f5"window"\fP).
  251. The third argument is a pointer to a function that is called by
  252. the interpreter whenever it must query the size (in bytes) of
  253. an object of the type being defined.
  254. It is called with an \f5Object\fP as its argument.
  255. For vectors, the size function would be defined like this:
  256. .PG
  257. int Vector_Size (Object v) {
  258.     return sizeof (struct S_Vector) +
  259.         (VECTOR(v)\->size-1) * sizeof Object;
  260. }
  261. .PE
  262. .PP
  263. However, this does not seem reasonable for types where all objects
  264. have the same size.
  265. In this case, one would invoke \f5Define_Type()\fP with a size function
  266. of \f5NOFUNC\fP and a fourth argument indicating the (constant) size.
  267. Thus, in the case of the
  268. .S window
  269. type, one could write
  270. .PG
  271. T_Window = Define_Type (0, "window", NOFUNC, sizeof (struct S_Window), ...
  272. .PE
  273. .PP
  274. The fifth and sixth argument are pointers to functions that are
  275. called by the interpreter to query whether two objects of the
  276. newly defined type are \f2operationally equivalent\fP or \f2equal\fP,
  277. respectively, that is, whenever the Scheme predicate
  278. .S eqv?
  279. or
  280. .S equal?
  281. is invoked on objects of this type.
  282. In cases where it is not useful to distinguish between equivalence
  283. and equality, only an equality function is defined,
  284. and this function is then used for both arguments.
  285. In the
  286. .S window
  287. example, the equality function can be defined like
  288. .PG
  289. int Window_Equal (Object a, Object b) {
  290.     return WINDOW(a)\->handle == WINDOW(b)\->handle;
  291. }
  292. .PE
  293. .PP
  294. When \f5NOFUNC\fP is passed to \f5Define_Type()\fP as the equivalence
  295. or equality function, two objects are never regarded as being
  296. equivalent or equal, respectively (that is,
  297. .S eqv?
  298. and
  299. .S equal?
  300. always return
  301. .S #f ).
  302. .PP
  303. The seventh argument to \f5Define_Type()\fP is the \f2print function\fP;
  304. this function is invoked by the interpreter when an object of the type
  305. being defined is to be printed (usually when
  306. .S display ,
  307. .S write ,
  308. or a similar function has been called).
  309. The print function receives as arguments the object to be printed,
  310. an output port, a flag indicating whether the object should be printed
  311. in an unquoted representation (in the sense of
  312. .S display ),
  313. and the
  314. currently remaining \f2print depth\fP and \f2print length\fP.
  315. The simplest way to define a \f5Window_Print\fP function is like this:
  316. .PG
  317. /*ARGSUSED*/
  318. void Window_Print (Object w, Object port, Bool raw, int depth, int len) {
  319.     Printf (port, "#[window %lu]", WINDOW(w)\->handle);
  320. }
  321. .PE
  322. where \f5Printf\fP (note the capital P)
  323. is the Scheme version of the ``C''-library \f5printf\fP.
  324. Since the window handle and the memory address of a window on the heap
  325. are equally useless sources of information, one could also print
  326. \f5POINTER(x)\fP (using the same format string).
  327. .PP
  328. The final argument of \f5Define_Type()\fP is the so-called \f2visit
  329. function\fP.
  330. This function is invoked by the interpreter during garbage collection
  331. when it traverses objects in order to determine the set of currently
  332. reachable (or accessible) objects.
  333. This function is only required for types where the objects have
  334. components of type \f5Object\fP, since in this case the reachability search of
  335. the interpreter must be ``directed'' to the objects that can be
  336. reached from the one being examined.
  337. For other types, \f5NOFUNC\fP is used instead.
  338. The visit function is called with the address of an \f5Object\fP
  339. and a pointer to a function; this function must be called for each
  340. component of the object that is of type \f5Object\fP itself.
  341. Thus, for pairs (\f2cons cells\fP) one would define the visit function
  342. like this:
  343. .PG
  344. void Pair_Visit (Object *x, void (*f)(Object *)) {
  345.     (*f)(&PAIR(*x)\->car);
  346.     (*f)(&PAIR(*x)\->cdr);
  347. }
  348. .PE
  349. At this point we are able to provide the first workable version of
  350. the
  351. .S window
  352. type:
  353. .PG
  354. \f2% cat window.c\fP
  355. .sp .5
  356. #include <scheme.h>
  357. #include <window.h>
  358.  
  359. int T_Window;
  360.  
  361. struct S_Window {
  362.     Window handle;
  363. };
  364. #define WINDOW(obj) ((struct S_Window *)POINTER(obj))
  365.  
  366. int Window_Equal (Object a, Object b) {
  367.     return WINDOW(a)\->handle == WINDOW(b)\->handle;
  368. }
  369.  
  370. /*ARGSUSED*/
  371. void Window_Print (Object w, Object port, Bool raw, int depth, int len) {
  372.     Printf (port, "#[window %lu]", WINDOW(w)\->handle);
  373. }
  374.  
  375. void init_window () {
  376.     T_Window = Define_Type (0, "window", NOFUNC, sizeof (struct S_Window),
  377.         Window_Equal, Window_Equal, Window_Print, NOFUNC);
  378. }
  379. \f2%\fP
  380. .PE
  381. .C Loading a Type Definition
  382. .PP
  383. To be able to load a module containing a type definition into the
  384. Scheme interpreter, it must be compiled and (if applicable) linked
  385. together with the libraries used by the module:
  386. .SC
  387. cc -c \f2other-options\fP window.c
  388. ld -r -x window.o \f2other_objects\fP -l\f2window_library\fP
  389. mv a.out window.o
  390. .PE
  391. .PP
  392. Of course, the exact sequence of commands may vary between different
  393. machines and also depends on the programming language actually used
  394. (this example assumes ``C'').
  395. The resulting object file can then directly be loaded from within
  396. Scheme using
  397. .S "(load 'window.o)" .\**
  398. .FS
  399. Note that certain unusual machines and operating systems do not
  400. support loading of object modules.
  401. Here the object files containing extensions to the interpreter must
  402. be linked together with the interpreter statically.
  403. .FE
  404. In addition, after having loaded one or more modules, one might
  405. want to save an image of the interpreter to a file by means of
  406. the
  407. .S dump
  408. primitive to avoid the (possibly time-consuming)
  409. loading of the object modules in the future.
  410. .PP
  411. When an object file is loaded, the interpreter searches the file
  412. for functions whose names begin with ``init_''; these
  413. functions are called (without arguments) in the order in which
  414. they appear in the object file.
  415. Thus, by placing the call to \f5Define_Type()\fP into
  416. the \f5init_window()\fP function in the example above, we
  417. ensure that this call is executed at the time the file is
  418. loaded by the interpreter.
  419. Usual practice is to put one \f5init_\fP\f2module\fP function at
  420. the end of each module.
  421. .LP
  422. In addition, it may be useful to place a line like
  423. .PG
  424. P_Provide (Intern ("window.o"));
  425. .PE
  426. into the \f5init_window()\fP function; this is the
  427. ``C'' counterpart of the Scheme expression
  428. .SC
  429. (provide 'window.o)
  430. .PE
  431. .PP
  432. A Scheme program which makes use of the window type can
  433. then conditionally load the corresponding object file by
  434. evaluating
  435. .SC
  436. (require 'window.o)
  437. .PE
  438. .C Creating Scheme Objects
  439. .PP
  440. So far we have seen how a new data type
  441. .S window
  442. can be defined and made
  443. known to the Scheme interpreter, but we are not yet able to create 
  444. any objects of this type.
  445. Creation of a Scheme object involves allocation and initialization
  446. of a suitable number of bytes on the heap and allocation of an
  447. \f5Object\fP ``handle''; the ID of the object's type must be written into
  448. the tag field of the \f5Object\fP, and the pointer field must
  449. be initialized to point to the starting address of the newly
  450. allocated object on the heap.
  451. .PP
  452. All this is done by the function \f5Alloc_Object\fP.
  453. The arguments to \f5Alloc_Object\fP are the size of the newly
  454. allocated Scheme object in bytes, the type of the object, and a flag
  455. indicating whether the object is considered constant.
  456. Constant objects may be placed in a read-only portion of the memory
  457. by \f5Alloc_Object()\fP.
  458. The return value of \f5Alloc_Object()\fP is the new Scheme object,
  459. i.\|e. the \f5Object\fP handle.
  460. If \f5Alloc_Object()\fP runs out of heap space when allocating a
  461. new object, it triggers a garbage collection; there is no need to
  462. check the result of \f5Alloc_Object()\fP.
  463. .PP
  464. The function to create a Scheme
  465. .S window
  466. can be written like this:
  467. .PG
  468. Object Make_Window (Window id) {
  469.     Object w;
  470. .sp .5
  471.     w = Alloc_Object (sizeof (struct S_Window), T_Window, 0);
  472.     WINDOW(w)\->handle = id;
  473.     return w;
  474. }
  475. .PE
  476. .QP
  477. \s-1\f3Rule:\fP All components of a Scheme object that are of
  478. the type \f5Object\fP themselves must be initialized with
  479. a valid \f5Object\fP immediately after the object has been
  480. allocated (typically, \f5Null\fP is used as a place-holder when the
  481. ``real'' values are not yet available).\s0
  482. .sp .5
  483. .PP
  484. The Scheme interpreter already provides a number of similar \f5Make_\fP
  485. functions the most interesting of which are
  486. .PG
  487. Object Make_Integer (int i)
  488. Object Make_Fixnum (int i)  \f2[i must fit into a ``fixnum'']\fP
  489. Object Make_Reduced_Flonum (double f)
  490. Object Make_Char (unsigned char c)
  491. Object Make_Vector (int length, Object init)
  492. Object Make_String (char *string, int length)
  493. .PE
  494. .PP
  495. The second argument of \f5Make_Vector()\fP is used to initialize
  496. all elements of the vector.
  497. The \f5length\fP argument of \f5Make_String()\fP may be larger than
  498. the actual length of the (null-terminated) \f5string\fP argument.
  499. Since the string passed to \f5Make_String()\fP must be null-terminated,
  500. strings containing null bytes have to be created like this:
  501. .PG
  502. Object s = Make_String ("", length);
  503. bcopy (\f2string_with_nulls\fP, STRING(s)\->data, length);
  504. .PE
  505. .C Defining Primitive Procedures
  506. .PP
  507. The \f5Make_Window()\fP function alone is not of much use; after all,
  508. it is only a ``C''-function, not a primitive procedure that can be
  509. called from within Scheme.
  510. A primitive procedure that creates and returns an object of type
  511. .S window
  512. could be defined like this:
  513. .PG
  514. Object P_Make_Window (Object x, Object y, Object width, Object height) {
  515.     Window id;
  516. .sp .5
  517.     id = Window_Create (Get_Integer (x), Get_Integer (y),
  518.         Get_Integer (width), Get_Integer (height));
  519.     return Make_Window (id);
  520. }
  521. .PE
  522. .QP
  523. \s-1\f3Naming Convention:\fP The names of primitive procedures start
  524. with \f5P_\fP followed by the name of the procedure in Scheme with
  525. all hyphens converted to underscores and all words capitalized.
  526. For instance, the ``C''-function implementing the Scheme procedure
  527. .S delete-all-buffers
  528. would be called \f5P_Delete_All_Buffers()\fP.\s0
  529. .LP
  530. .QP
  531. \s-1\f3Rule:\fP All primitive procedures must return an \f5Object\fP.
  532. The non-printing object \f5Void\fP can be returned in absence of a
  533. meaningful return value.
  534. All arguments of a primitive procedure either are of type \f5Object\fP,
  535. or the procedure has exactly two arguments, an integer and an array
  536. of \f5Object\fPs.\s0
  537. .sp .5
  538. .PP
  539. The function \f5Get_Integer()\fP converts a Scheme integer to a ``C''
  540. \f5int\fP.
  541. An error is signalled when \f5Get_Integer()\fP is called with an
  542. \f5Object\fP other than an integer (thus, the arguments of
  543. \f5P_Make_Window()\fP need not explicitly be type-checked)
  544. or when the integer does not fit into an \f5int\fP.
  545. .LP
  546. Finally, we must arrange that when a Scheme expression like
  547. .SC
  548. (make-window 0 0 400 600)
  549. .PE
  550. is evaluated, the corresponding ``C''-function \f5P_Make_Window()\fP
  551. is invoked by the interpreter.
  552. More precisely, the Scheme symbol with the name \f5make-window\fP
  553. must be bound to an object of type ``primitive procedure'' which
  554. points to the function \f5P_Make_Window()\fP (just like the way
  555. .S car
  556. is bound to the primitive procedure ``car'' in the
  557. interpreter's global environment).
  558. In addition, we must define whether the arguments in a call to
  559. .S make-window
  560. must be evaluated (they must) and whether it
  561. is acceptable to call this procedure with a variable or arbitrary
  562. number of arguments (it is not acceptable), that is, the
  563. \f2calling discipline\fP must be defined.
  564. This is accomplished by the function \f5Define_Primitive()\fP:
  565. .PG
  566. Define_Primitive (P_Make_Window, "make-window", 4, 4, EVAL);
  567. .PE
  568. .PP
  569. The first argument is a pointer to the ``C''-function that implements
  570. the primitive procedure;
  571. the seconds argument is the name of the primitive procedure (that is, 
  572. the name of the symbol to which the procedure is to be bound);
  573. the third and fourth argument are the minimum and maximum number
  574. of arguments of the primitive procedure, and
  575. the last argument is the calling discipline.
  576. The natural place to go for the call to \f5Define_Primitive()\fP is
  577. the \f5init_window()\fP function; this ensures that the call is
  578. executed when the module is loaded.
  579. .PP
  580. Three calling disciplines are supported by \f5Define_Primitive()\fP:
  581. \f5EVAL\fP, \f5VARARGS\fP, and \f5NOEVAL\fP.
  582. Procedures of type \f5EVAL\fP are the most common ones; they are
  583. declared with individual arguments like \f5P_Make_Window()\fP above.
  584. \f5VARARGS\fP procedures receive two arguments, an \f5int\fP holding
  585. the number of actual arguments, and an array of evaluated \f5Object\fPs.
  586. For instance, the function implementing the primitive
  587. .S list
  588. could be declared as
  589. .PG
  590. Object P_List (int argc, Object *argv) {
  591.     ...
  592. }
  593. .PE
  594. and the corresponding call to \f5Define_Primitive()\fP would be
  595. .PG
  596. Define_Primitive (P_List, "list", 0, MANY, VARARGS);
  597. .PE
  598. The special constant \f5MANY\fP indicates that there is no upper bound
  599. on the number of arguments.
  600. .PP
  601. \f5NOEVAL\fP is used for \f2special forms\fP; the function
  602. implementing the special form receives one argument of type
  603. \f5Object\fP, a list holding the unevaluated arguments
  604. (thus, the argument of such a function has either the type
  605. \f5T_Pair\fP, or the argument is \f5Null\fP when it has been called
  606. with no arguments).
  607. .QP
  608. \s-1\f3Bug:\fP In the current version of the interpreter, primitive
  609. procedures of type \f5EVAL\fP must have a fixed number of
  610. arguments (that is, the minimum and maximum number of arguments
  611. must be identical).
  612. The type \f5VARARGS\fP must be used when a variable number of
  613. arguments is desired.\s0
  614. .sp .5
  615. .PP
  616. Scheme-conventions require a type predicate to be defined for
  617. each new Scheme type.
  618. This can be done quite easily like this:
  619. .PG
  620. Object P_Windowp (Object x) {
  621.     return TYPE(x) == T_Window ? True : False;
  622. }
  623. \f2[``p''-suffix denotes a predicate; ``?'' in Scheme]\fP
  624. .PE
  625. The corresponding call to \f5Define_Primitive()\fP is
  626. .PG
  627. Define_Primitive (P_Windowp, "window?", 1, 1, EVAL);
  628. .PE
  629. \f5True\fP and \f5False\fP are pre-defined variables of type \f5Object\fP
  630. representing the Scheme constants
  631. .S #t
  632. and
  633. .S #f .
  634. .LP
  635. The primitive procedure to destroy a window could be written like this:
  636. .PG
  637. Object P_Destroy_Window (Object w) {
  638.     Check_Type (w, T_Window);
  639.     Window_Destroy (WINDOW(w)\->handle);
  640.     return Void;
  641. }
  642.  ...
  643. Define_Primitive (P_Destroy_Window, "destroy-window", 1, 1, EVAL);
  644. .PE
  645. .C Improving the Example
  646. .PP
  647. There are couple of problems with the first version of the
  648. .S window
  649. implementation.
  650. One of them is obvious \(em it is possible that \f5Window_Destroy()\fP is
  651. called with the handle of a window that has already been destroyed
  652. by a previous call to \f5Window_Destroy()\fP.
  653. Although it seems that this can be easily fixed, it turns out to be
  654. the instance of a more fundamental problem.
  655. Consider the following code fragment:
  656. .SC
  657. (define w (make-window 0 0 400 600))
  658. (destroy-window w)
  659.  ...
  660. (define w2 (make-window 100 100 300 300))
  661. .PE
  662. .PP
  663. Since the handle of the first window has been deallocated by the window
  664. system due to the call to
  665. .S destroy-window ,
  666. it is perfectly
  667. legal for the next call to \f5Window_Create()\fP to return the same
  668. handle again.
  669. Now we have the fatal situation that the object
  670. .S w
  671. points to
  672. a window (since its handle is the same than that of
  673. .S w2 ),
  674. although it has actually been destroyed by a call to
  675. .S destroy-window .
  676. .S w
  677. and
  678. .S w2
  679. are even
  680. .S equal? ,
  681. and a second call to
  682. .S "(destroy-window w)"
  683. causes
  684. .S w2
  685. (a completely unrelated window) to disappear.
  686. .PP
  687. The source of this problem is the fact that each Scheme object has a
  688. potentially unlimited lifetime; one cannot ``force'' an object
  689. to die (e.\|g. in the function
  690. .S destroy-window )
  691. as long as it is still accessible.
  692. Thus, the
  693. .S window
  694. object
  695. .S w
  696. in the scenario above is in a special ``undead'' state \(em the ``Scheme-side''
  697. of the object is still alive, while the resource in the window system
  698. that the object is pointing to has already been deallocated.
  699. .PP
  700. One way to fix the
  701. .S window
  702. module (at least for the time being)
  703. is to ``mark'' undead windows by overwriting their \f5handle\fP
  704. component with a special value for which we can guarantee that
  705. it is never returned by a call to \f5Window_Create()\fP.
  706. Assuming that there is such a constant and that it is called \f5None\fP
  707. (it might be defined as zero), we can rewrite \f5P_Destroy_Window()\fP
  708. and \f5Window_Equal()\fP as follows:
  709. .PG
  710. Object P_Destroy_Window (Object w) {
  711.     Check_Type (w, T_Window);
  712.     if (WINDOW(w)\->handle != None) {
  713.     Window_Destroy (WINDOW(w)\->handle);
  714.     WINDOW(w)\->handle = None;
  715.     }
  716.     return Void;
  717. }
  718. .sp
  719. int Window_Equal (Object a, Object b) {
  720.     if (WINDOW(a)\->handle == None |\ | WINDOW(b)\->handle == None)
  721.     return 0;
  722.     return WINDOW(a)\->handle == WINDOW(b)\->handle;
  723. }
  724. .PE
  725. .PP
  726. According to the new version of the equality function, an undead
  727. window is different from any other window (even from other undead
  728. windows).
  729. In addition to these changes, one might want to modify the
  730. \f5Window_Print()\fP function to reflect the fact that a window being
  731. printed has been destroyed.
  732. .C More Improvements (and More Problems)
  733. .PP
  734. Assume that the window library provides a function
  735. .PG
  736. int List_Windows (Window **ret)
  737. .PE
  738. which returns (through the \f5ret\fP argument) a list of all windows
  739. that are currently active on the display; the integer return value is the
  740. number of windows stored into the \f5ret\fP array.
  741. This allows us to write a primitive procedure
  742. .S list-windows
  743. that returns a vector of
  744. .S window
  745. objects like this:
  746. .PG
  747. Object P_List_Windows () {
  748.     int n, i;
  749.     Window *ret;
  750.     Object v;
  751.     GC_Node;
  752. .sp .5
  753.     n = List_Windows (&ret);
  754.     v = Make_Vector (n, Null);
  755.     GC_Link (v);
  756.     for (i = 0; i < n; i++)
  757.     VECTOR(v)\->data[i] = Make_Window (ret[i]);
  758.     GC_Unlink;
  759.     return v;
  760. }
  761.  ...
  762. Define_Primitive (P_List_Windows, "list-windows", 0, 0, EVAL);
  763. .PE
  764. .PP
  765. The function allocates a vector of the length returned by the
  766. call to \f5List_Windows()\fP; the elements of the vector are
  767. initialized with \f5Null\fP (any valid \f5Object\fP could be used
  768. here, since the elements are overwritten by the immediately
  769. following code anyway).
  770. Then, each \f5Window\fP handle returned by \f5List_Windows()\fP is
  771. converted into a Scheme object and stored into the corresponding
  772. vector slot.
  773. .PP
  774. As mentioned above, the call to \f5Make_Window()\fP can cause
  775. a garbage collection to be performed, since it involves a call to
  776. \f5Alloc_Object()\fP.
  777. The garbage collector basically traverses all Scheme objects that
  778. are currently accessible and marks them as non-garbage.
  779. Everything else is declared as garbage and overwritten by the
  780. ``good'' objects during the heap compaction.
  781. Now imagine that one of the invocations of \f5Make_Window()\fP
  782. in \f5P_List_Windows()\fP triggers the garbage collector, since
  783. there is not enough space left on the heap to store one of the
  784. .S window
  785. objects.
  786. The newly allocated vector is only accessible through
  787. the local variable \f5v\fP (no other object is pointing to it),
  788. thus the garbage collector is unable to find and mark the vector
  789. as non-garbage, as it does not traverse the ``C'' runtime stack
  790. during the accessibility search.
  791. As the result, the vector as well as all windows stored in
  792. the vector so far would be overwritten.
  793. .PP
  794. This can be avoided by temporarily storing a reference to the 
  795. vector into a global list which is guaranteed to be visited
  796. by the garbage collector.
  797. When \f5P_List_Windows()\fP returns, it is no longer necessary
  798. to ``protect'' the vector from being garbage-collected, since
  799. it either becomes accessible, e.\|g. when
  800. .S list-windows
  801. is called like this:
  802. .SC
  803. (define v (list-windows))   ; the vector is stored into a variable
  804. .PE
  805. or
  806. .SC
  807. ; the vector becomes accessible through another function's argument:
  808. .sp .5
  809. (frobnicate-windows (list-windows))
  810. .PE
  811. or the vector and its contents really become garbage, namely
  812. when the primitive procedure is called like this:
  813. .SC
  814. (list-windows)   ; the return value is thrown away
  815. .PE
  816. .PP
  817. An object can be ``linked'' into the global accessibility list by means
  818. of the macro \f5GC_Link()\fP.
  819. Since it is frequently necessary to protect several objects from
  820. garbage collection (typically several local variables of a function) at
  821. the same time, there are also macros called \f5GC_Link2()\fP,
  822. \f5GC_Link3()\fP, etc. to facilitate protection of two or more
  823. objects.
  824. A call to the macro \f5GC_Link\fP\f2n\fP\f5()\fP must be preceded by the
  825. declaration \f5GC_Node\fP\f2n\fP (as can be seen in the example
  826. above).
  827. The macro \f5GC_Unlink\fP removes all objects from the accessibility
  828. list that have been added to the list by a preceding call to
  829. \f5GC_Link()\fP.
  830. Calls to \f5GC_Link()\fP and \f5GC_Unlink\fP cannot be nested.
  831. .QP
  832. \s-1\f3Rule:\fP It is imperative that an object has been fully
  833. initialized before it is protected from garbage collection.\s0
  834. .sp .5
  835. .PP
  836. When implementing \f5P_List_Windows()\fP, one may be tempted to
  837. write the for-loop something like this:
  838. .PG
  839. Object *p;
  840.  ...
  841. for (p = VECTOR(v)\->data; n > 0; n\(mi\(mi)
  842.     *p++ = Make_Window (*ret++);
  843. .PE
  844. .PP
  845. That is, one might want to use a pointer to step through the
  846. vector slots rather than use an index.
  847. However, this would not work.
  848. Since garbage collection involves moving the ``good'' objects
  849. to a different location on the heap,
  850. the contents of \f5v\fP changes during garbage collection
  851. (the contents of the pointer field of \f5v\fP, to be precise).
  852. Thus, the pointer \f5p\fP would suddenly point to an invalid
  853. location (probably into a different Scheme object), although it
  854. has been properly initialized.
  855. Therefore it is essential that \f5VECTOR(v)\->data\fP is evaluated
  856. again at each iteration.
  857. .QP
  858. \s-1\f3Rule:\fP Never keep ``C'' pointers to Scheme objects over
  859. a function call possibly invoking a garbage collection.\s0
  860. .sp .5
  861. .PP
  862. The fact that
  863. .S list-windows
  864. returns a vector of windows may have disadvantages.
  865. It could be rewritten to return a list instead like this:
  866. .PG
  867. Object P_List_Windows () {
  868.     int n;
  869.     Window *ret;
  870.     Object list, tail;
  871.     GC_Node2;
  872. .sp .5
  873.     n = List_Windows (&ret);
  874.     list = tail = P_Make_List (Make_Fixnum (n), Null);
  875.     GC_Link2 (list, tail);
  876.     for ( ; n > 0; n\(mi\(mi) {
  877.     Car (tail) = Make_Window (*ret++);
  878.     tail = Cdr (tail);
  879.     }
  880.     GC_Unlink;
  881.     return list;
  882. }
  883. .PE
  884. .PP
  885. Note that there is no \f5Make_List()\fP function.
  886. But there is a
  887. .S make-list
  888. primitive procedure which does what we want, so we can use that instead.
  889. However, since it is a primitive procedure, all arguments must be
  890. of type \f5Object\fP (which is the reason for the call to
  891. \f5Make_Fixnum()\fP).
  892. \f5Car()\fP and \f5Cdr()\fP are macros; they are defined as follows:
  893. .PG
  894. #define Car(x) PAIR(x)\->car
  895. #define Cdr(x) PAIR(x)\->cdr
  896. .PE
  897. .PP
  898. Although the version of
  899. .S list-windows
  900. using a list is slightly
  901. more complex than the one using a vector, it has the advantage that
  902. lists are ``favored'' by Scheme, that is, there are more primitive
  903. procedures that work on lists than on vectors.
  904. For instance, one can use
  905. .S for-each
  906. to apply a procedure to all elements of a list:
  907. .SC
  908. (for-each destroy-window (list-windows))
  909. .PE
  910. .PP
  911. On the other hand, vectors consume less memory than lists and can
  912. be manipulated more efficiently, since the elements of a vector
  913. are stored contiguously and the length of a vector can be queried
  914. in constant time.
  915. .PP
  916. Unfortunately, no matter whether vectors or lists are used,
  917. there is a serious problem with
  918. .S list-windows
  919. (or rather with the entire
  920. .S window
  921. module).
  922. Consider the following piece of code:
  923. .SC
  924. (define w (make-window 0 0 400 600))  ; the only call to make-window
  925. (define v (list-windows))
  926. (define w2 (vector-ref v 0))
  927. (destroy-window w)
  928. .PE
  929. .PP
  930. Before
  931. .S destroy-window
  932. is called,
  933. .S w
  934. and
  935. .S w2
  936. point to the same window (since only one window has been created).
  937. The call to
  938. .S destroy-window
  939. instructs the window system to deallocate the window and marks
  940. .S w
  941. accordingly.
  942. However,
  943. .S w2
  944. still contains the handle of the window that has
  945. just been destroyed, so that \f5Window_Destroy()\fP can be called
  946. again with the same window handle.
  947. Even worse, another call to
  948. .S make-window
  949. can return a window with the same handle, so that the situation
  950. gets completely inconsistent.
  951. This looks like the mess which we thought we had fixed in the
  952. previous chapter.
  953. .PP
  954. The problem seems to lie in the fact that it is possible at all
  955. that two distinct Scheme objects can point to the same ``physical''
  956. window.
  957. The problem would go away if it could be ensured that there is
  958. exactly one object of type
  959. .S window
  960. for each window handle.
  961. In the scenario above, this would imply that
  962. .S w
  963. and the window returned by
  964. .S list-windows
  965. are not only
  966. .S equal? ,
  967. but also equal in the sense of
  968. .S eq? .
  969. If this could be ensured, it would, of course, no longer be necessary
  970. to define a \f5Window_Equal()\fP function at all, since
  971. .S "(equal? w1 w2)"
  972. would imply
  973. .S "(eq? w1 w2)"
  974. for all windows
  975. .S w1
  976. and
  977. .S w2
  978. (and
  979. .S "(eq? w1 w2)"
  980. implies
  981. .S "(equal? w1 w2)"
  982. anyway).
  983. .PP
  984. The problem can be solved by modifying \f5Make_Window()\fP such that
  985. it checks whether there is already a Scheme window with the given
  986. window handle and, if this is true, returns this object rather than
  987. allocating a new one.
  988. To achieve this, we first define a function that stores a
  989. .S window
  990. object in a global list (a \f2pool\fP) of windows:
  991. .PG
  992. Object pool[MAX_WINDOWS];  \f2[MAX_WINDOWS is defined by the window library]\fP
  993.  
  994. void Register_Window (Object w) {
  995.     Object *p;
  996. .sp .5
  997.     for (p = pool; p < pool+MAX_WINDOWS && !Nullp (*p); p++)
  998.     ;
  999.     if (Nullp (*p))
  1000.     *p = w;
  1001.     else
  1002.     error ...
  1003. }
  1004. .PE
  1005. \f5pool\fP is initialized in \f5init_window\fP like this:
  1006. .PG
  1007. Object *p;
  1008. .sp .5
  1009. for (p = pool; p < pool+MAX_WINDOWS; p++) *p = Null;
  1010. .PE
  1011. .PP
  1012. \f5Nullp()\fP is a macro that simply tests whether its argument
  1013. is equal to \f5Null\fP.
  1014. In practice, it might be useful to employ a hash function or some
  1015. other algorithm that is faster than linear search (especially
  1016. if the number of different objects can get large).
  1017. .PP
  1018. In addition, we need a function that looks up an object with a
  1019. given window handle in the pool and, if one is found, returns it
  1020. (otherwise \f5Null\fP is returned):
  1021. .PG
  1022. Object Find_Window (Window handle) {
  1023.     Object *p;
  1024. .sp .5
  1025.     for (p = pool; p < pool+MAX_WINDOWS; p++)
  1026.     if (!Nullp (*p) && WINDOW(*p)\->handle == handle)
  1027.         return *p;
  1028.     return Null;
  1029. }
  1030. .PE
  1031. Now \f5Make_Window()\fP can be modified like this:
  1032. .PG
  1033. Object Make_Window (Window id) {
  1034.     Object w;
  1035. .sp .5
  1036.     w = Find_Window (id);
  1037.     if (Nullp (w)) {
  1038.     w = Alloc_Object (sizeof (struct S_Window), T_Window, 0);
  1039.     WINDOW(w)\->handle = id;
  1040.     Register_Window (w);
  1041.     }
  1042.     return w;
  1043. }
  1044. .PE
  1045. The last step is to remove an object from the pool when
  1046. .S destroy-window
  1047. is called:
  1048. .PG
  1049. void Deregister_Window (Object w) {
  1050.     Object *p;
  1051. .sp .5
  1052.     for (p = pool; p < pool+MAX_WINDOWS; p++)
  1053.     if (EQ(*p, w)) {
  1054.         *p = Null;
  1055.         break;
  1056.     }
  1057. }
  1058. .PE
  1059. .PP
  1060. \f5EQ\fP is a macro that checks whether two objects are equal
  1061. in the sense of the
  1062. .S eq?
  1063. predicate (that is, whether their pointer fields point to the same location).
  1064. The line
  1065. .PG
  1066. Deregister_Window (w);
  1067. .PE
  1068. must then be added to \f5P_Destroy_Window()\fP.
  1069. .C Garbage Collection Revisited
  1070. .LP
  1071. Consider the following code fragments:
  1072. .SC
  1073. (define w
  1074.   (make-window 0 0 400 600))
  1075. (set! w 'foo)
  1076. .PE
  1077. or
  1078. .SC
  1079. (make-window 0 0 400 600)
  1080. .PE
  1081. .PP
  1082. In the first scenario, a window is created and assigned to a variable;
  1083. immediately after that, the variable is overwritten by something else.
  1084. In the second example, the result of
  1085. .S make-window
  1086. is not even saved into a variable.
  1087. In both cases, the newly created window has become inaccessible to
  1088. the Scheme program; either by destroying the only reference to it
  1089. or by discarding the return value of
  1090. .S make-window .
  1091. Thus, it has become impossible to destroy the window from within
  1092. Scheme \(em it remains on the screen until it is removed by other means
  1093. (e.\|g., when the window system is terminated).
  1094. Of course, one could invoke
  1095. .S list-windows
  1096. to obtain a new 
  1097. reference to the window, but it seems desirable to have a general
  1098. solution to this problem that is independent of the existence of
  1099. .S list-windows .
  1100. .PP
  1101. For instance, it would be useful if the interpreter could
  1102. terminate a window (by a call to \f5Window_Destroy()\fP) automatically
  1103. when it can prove that all references to a window have been lost.
  1104. This can obviously be done with the help of the garbage collector,
  1105. since the garbage collector is able to determine the set of
  1106. all accessible objects anyway.
  1107. Thus, at the point the garbage collector is finished, we just need
  1108. to go through the set of active windows and destroy those windows
  1109. that have not been moved by the garbage collector (that is, the
  1110. windows that have not been found during the accessibility search).
  1111. Fortunately, the
  1112. .S window
  1113. code already maintains the set of
  1114. active windows (the \f5pool\fP array), so the function that
  1115. terminates all inaccessible windows can be written like this:
  1116. .PG
  1117. void Terminate_Windows () {
  1118.     Object *p, *tag;
  1119. .sp .5
  1120.     for (p = pool; p < pool+MAX_WINDOWS; p++) {
  1121.     if (Nullp (*p))
  1122.         continue;
  1123.     tag = (Object *)POINTER(*p);
  1124.     if (TYPE(*tag) == T_Broken_Heart)
  1125.         SETPOINTER(*p, POINTER(*tag));
  1126.     else
  1127.         (void)P_Destroy_Window (*p);
  1128.     }
  1129. }
  1130. .PE
  1131. .PP
  1132. When the garbage collector ``visits'' an object, it first determines
  1133. the location of the object on the heap by looking at the pointer
  1134. field, then it copies the object to its new location.
  1135. The pointer field of the \f5Object\fP handle is updated so that
  1136. it points to the object's new location.
  1137. The memory that has been occupied by the object up to now is
  1138. overwritten by a special object called \f2broken heart\fP;
  1139. the pointer field of the broken heart is initialized to point
  1140. to the object's new location.
  1141. Thus, when the garbage collector encounters another \f5Object\fP
  1142. handle that points to the object just moved, it only needs to
  1143. update the pointer field with the new address stored in
  1144. the broken heart (the broken heart indicates that the object
  1145. has already been moved).
  1146. .PP
  1147. The \f5Terminate_Windows()\fP function simply looks
  1148. at the memory pointed to by each window's pointer field.
  1149. When a broken heart is stored in the window (that is, in the
  1150. \f5S_Window\fP structure), the window has obviously been moved
  1151. by the garbage collector.
  1152. This indicates that there must exist another reference to that window,
  1153. otherwise it had not been moved.
  1154. Thus, all we have to do is update the pointer field so that
  1155. it points to the window's new location
  1156. (the macro \f5SETPOINTER()\fP is used to store a value into
  1157. the pointer field of a variable of type \f5Object\fP).
  1158. Otherwise, when the window has not been overwritten by a broken heart,
  1159. it can be destroyed, since no more \f5Object\fP handles pointing to it
  1160. exist (except than the one in the \f5pool\fP).
  1161. Note that \f5P_Destroy_Window()\fP also removes the window from
  1162. the \f5pool\fP.
  1163. .PP
  1164. The first element of the underlying structure of a type must
  1165. obviously be of type \f5Object\fP, since the garbage collector
  1166. stores a broken heart at the beginning of an object (otherwise
  1167. it could be possible that the bit pattern at the beginning of
  1168. an object is accidentally interpreted as a broken heart).
  1169. Thus, \f5S_Window\fP must be redefined like this:
  1170. .PG
  1171. struct S_Window {
  1172.     Object tag;
  1173.     Window handle;
  1174. };
  1175. .PE
  1176. .PP
  1177. Since we currently do not have any use for a component of type
  1178. \f5Object\fP, we simply set it to \f5Null\fP in \f5Make_Window()\fP:
  1179. .PG
  1180. WINDOW(w)\->tag = Null;
  1181. .PE
  1182. .QP
  1183. \s-1\f3Rule:\fP All types must start with a component of
  1184. type \f5Object\fP, even if it is unused.
  1185. This component must be properly initialized immediately after creation of
  1186. an object (e.\|g. with \f5Null\fP).\s0
  1187. .sp .5
  1188. .PP
  1189. Finally we have to ensure that \f5Terminate_Windows()\fP is called
  1190. by the interpreter immediately after each garbage collection,
  1191. but before normal execution is resumed.
  1192. This can be done by registering a so-called \f2after-GC function\fP:
  1193. .PG
  1194. Register_After_GC (Terminate_Windows);
  1195. .PE
  1196. .PP
  1197. \f5Register_After_GC()\fP can be called from the window module's
  1198. initialization function.
  1199. An ``after-GC function'' must not cause a garbage collection and
  1200. it must return normally.
  1201. .PP
  1202. Note that the \f5pool\fP array is not ``GC-protected'' like the
  1203. local variables in the \f5P_List_Windows()\fP function above.
  1204. This is not necessary, because the pointer fields of the
  1205. \f5Object\fPs in the pool are updated ``by hand'' immediately
  1206. after each garbage collection.
  1207. If we had not written the \f5Terminate_Windows()\fP function,
  1208. we certainly had to make sure that the \f5pool\fP is known
  1209. to the garbage collector so that the pointer fields of the
  1210. .S window
  1211. objects are properly updated when a window has
  1212. been moved to a new location on the heap.
  1213. However, this cannot be done with \f5GC_Link()\fP and \f5GC_Unlink\fP,
  1214. since the scope of a \f5GC_Link()\fP is bounded by the body of the
  1215. function from which it is called.
  1216. While \f5GC_Link()\fP establishes a ``dynamic'' GC-protection and
  1217. is used to GC-protect local variables and function arguments,
  1218. we need a way to ``statically'' GC-protect an object (a global
  1219. variable).
  1220. This can be done with the macro
  1221. .PG
  1222. Global_GC_Link (obj)
  1223. .PE
  1224. where \f5obj\fP is a variable of type \f5Object\fP.
  1225. Unfortunately, the current version of the interpreter does not
  1226. support GC-protection of arrays of \f5Object\fPs (like the \f5pool\fP
  1227. array).
  1228. However, one could create a vector of \f5MAX_WINDOWS\fP elements
  1229. and use this vector to store the active windows rather than a
  1230. plain array of \f5Object\fPs.
  1231. The vector could then be GC-protected statically by a call to
  1232. \f5Global_GC_Link()\fP.
  1233. .QP
  1234. \s-1\f3Exercise:\fP Consider the following program fragment:
  1235. .SC
  1236. (list-windows)
  1237. (collect)   ; invoke the garbage collector
  1238. .PE
  1239. .QP
  1240. \s-1What happens?
  1241. How can this be fixed (which functions of the
  1242. .S window
  1243. module must be modified)?\s0
  1244. .sp .5
  1245. .C Defining Symbols
  1246. .PP
  1247. Suppose that the window system function \f5Window_Create()\fP has a
  1248. fifth argument that is used to determine the
  1249. color of the background of the newly created window.
  1250. The argument can be either \f5BgWhite\fP, \f5BgBlack\fP, or
  1251. \f5BgTransparent\fP; these symbols are defined as integer constants
  1252. in the window library's include file.
  1253. The primitive procedure
  1254. .S make-window
  1255. should be extended accordingly;
  1256. the additional argument can be one of the symbols
  1257. .S black ,
  1258. .S white ,
  1259. and
  1260. .S transparent .
  1261. This allows us to write expressions like
  1262. .SC
  1263. (define w (make-window 0 0 500 700 'white))
  1264. .PE
  1265. The new \f5P_Make_Window()\fP could be defined like this:
  1266. .PG
  1267. Object P_Make_Window (Object x, Object y, Object width, Object height,
  1268.     Object color) {
  1269.     Window id;
  1270.     int c;
  1271. .sp .5
  1272.     Check_Type (color, T_Symbol);
  1273.     if (EQ(color, Sym_Black))
  1274.     c = BgBlack;
  1275.     else if (EQ(color, Sym_White))
  1276.     c = BgWhite;
  1277.     else if (EQ(color, Sym_Transparent))
  1278.     c = BgTransparent;
  1279.     else
  1280.     Primitive_Error ("invalid background: ~s", color);
  1281.     id = Window_Create (Get_Integer (x), Get_Integer (y),
  1282.         Get_Integer (width), Get_Integer (height), c);
  1283.     return Make_Window (id);
  1284. }
  1285. .PE
  1286. .PP
  1287. \f5Primitive_Error()\fP invokes the Scheme error handler; it's arguments
  1288. are a format string (that can be passed to the primitive procedure
  1289. .S format )
  1290. and a variable number of \f5Object\fPs.
  1291. \f5Sym_Black\fP, \f5Sym_White\fP, and \f5Sym_Transparent\fP are of
  1292. type \f5Object\fP; they are initialized in \f5init_window()\fP like
  1293. this:
  1294. .PG
  1295.  ...
  1296. Define_Symbol (&Sym_Black, "black");
  1297. Define_Symbol (&Sym_White, "white");
  1298. Define_Symbol (&Transparent, "transparent");
  1299. .PE
  1300. .PP
  1301. The first argument to \f5Define_Symbol()\fP is a pointer to a
  1302. global variable of type \f5Object\fP, the second argument is
  1303. the \f2print name\fP of the newly created symbol.
  1304. The symbols created by the calls to \f5Define_Symbol()\fP are
  1305. properly GC-protected.
  1306. The above initializations could also be written like this:
  1307. .PG
  1308.  ...
  1309. Sym_Black = Intern ("black");
  1310. Global_GC_Link (Sym_Black);
  1311. \f2[etc.]\fP
  1312. .PE
  1313. .PP
  1314. \f5Intern()\fP is the ``Make'' function for symbols; it returns
  1315. an \f5Object\fP of type \f5T_Symbol\fP.
  1316. The reason why it does not follow the naming scheme of the other
  1317. ``Make'' functions is that it works in a slightly different way.
  1318. Symbols are maintained by the interpreter similarly to windows
  1319. in our example; a call to \f5Intern()\fP does not necessarily create
  1320. a new object on the heap.
  1321. Scheme guarantees that two symbols with the same name are equal
  1322. in the sense of the
  1323. .S eq?
  1324. predicate (the analogous rule in the
  1325. .S window
  1326. example is that two windows with the
  1327. same handle are equal
  1328. .S eq? -wise
  1329. \(em which is the
  1330. reason why we introduced \f5Register_Window()\fP and \f5Find_Window()\fP).
  1331. The interpreter internally maintains a hashed symbol table (often called
  1332. \f2the obarray\fP or \f2the oblist\fP) similar to the window \f5pool\fP
  1333. in the example above.
  1334. \f5Intern()\fP essentially searches the oblist for a symbol with
  1335. the same name and, if it is found, simply returns a new \f5Object\fP
  1336. handle pointing to that symbol.
  1337. If no symbol with the given name is found, it creates one (this
  1338. involves a call to \f5Alloc_Object()\fP) and enters it into the
  1339. symbol table.
  1340. Certainly \f5Make_Window()\fP could also be written like this:
  1341. .PG
  1342.  ...
  1343. if (EQ(color, Intern ("black"))
  1344.     c = BgBlack;
  1345. else if (EQ(color, Intern ("white"))
  1346.     c = BgWhite;
  1347. \f2[etc.]\fP
  1348. .PE
  1349. .PP
  1350. The advantage over the first version is that there is no need for
  1351. the \f5Sym_\fP variables and the calls to \f5Define_Symbol()\fP.
  1352. On the other hand, it is less efficient, since \f5Intern\fP
  1353. must search the symbol each time it is called.
  1354. In addition, \f5Intern()\fP can trigger a garbage collection
  1355. (when a new symbol must be created on the heap), so that the arguments of
  1356. \f5P_Make_Window()\fP have to be GC-protected in the version using
  1357. \f5Intern()\fP.
  1358. .PP
  1359. When a primitive procedure maintains a large number of symbols
  1360. or when Scheme symbols must frequently be converted to ``C-symbols''
  1361. (integer constants), it might be useful to define a general conversion
  1362. like this:
  1363. .PG
  1364. struct symbol { char *name; int val; };
  1365.  
  1366. int Scheme_To_C_Symbol (Object sym, struct symbol *p) {
  1367.     Object name;
  1368. .sp .5
  1369.     Check_Type (sym, T_Symbol);
  1370.     name = SYMBOL(sym)\->name;
  1371.     while (p\->name != 0
  1372.        && strncmp (p\->name, STRING(name)\->data, STRING(name)\->size) != 0)
  1373.     p++;
  1374.     if (p\->name == 0)
  1375.     Primitive_Error ("invalid symbol: ~s", sym);
  1376.     return p\->val;
  1377. }
  1378. .PE
  1379. The function could then be used like this:
  1380. .PG
  1381. struct symbol background\|[\|] = {
  1382.     "black",        BgBlack,
  1383.     "white",        BgWhite,
  1384.     "transparent",    BgTransparent,
  1385.     0, 0
  1386. };
  1387.  
  1388. Object P_Make_Window (..., Object color) {
  1389.     int c;
  1390.      ...
  1391.     c = Scheme_To_C_Symbol (color, background);
  1392.      ...
  1393. .PE
  1394. .C Defining Variables
  1395. .PP
  1396. Sometimes it is desirable to interact with the user of a module
  1397. not only through primitive procedures, but also through Scheme
  1398. variables defined by the module.
  1399. For instance, suppose that the hypothetical window system handles
  1400. errors by calling a user-supplied error function whenever an
  1401. error situation is encountered (such as ``out of window resources''
  1402. or ``out-of-bounds argument passed to a library function'').
  1403. The window system library could export a variable
  1404. .PG
  1405. void (*Error_Function)(int error);
  1406. .PE
  1407. that is initially zero and can be set by the user of the library.
  1408. In case of an error, when the variable is non-zero, the window system
  1409. invokes the \f5Error_Function\fP with an error number identifying the
  1410. error.
  1411. .PP
  1412. An obvious improvement would be to allow the user of the window
  1413. module to bind a Scheme function to the window system's error
  1414. handler.
  1415. This function could then invoke the standard Scheme error handler
  1416. (enabling the user to analyze the situation using the debugger)
  1417. or handle the error in an ``intelligent'' way.
  1418. This can be implemented by letting the window module declare
  1419. a variable
  1420. .S window-error-handler
  1421. and then define an
  1422. ``interface'' function that acts as the ``glue'' between
  1423. the ``C'' error handler and the Scheme error handler.
  1424. The address of the interface function is assigned to
  1425. \f5Error_Function\fP at the point the window module is initialized.
  1426. When the function is called by the window system to report an
  1427. error, it checks whether the Scheme variable
  1428. .S window-error-handler
  1429. is currently bound to a function (an object of type \f5T_Compound\fP)
  1430. and, if this is the case, calls
  1431. the function with the error code argument.
  1432. Of course, the (numeric) argument provided by the window system
  1433. must be converted into a representation that is more useful to 
  1434. a Scheme error handler (e.\|g. a symbol or a string).
  1435. .LP
  1436. A variable can be defined by means of the function
  1437. .PG
  1438. void Define_Variable (Object *handle, char *name, Object init);
  1439. .PE
  1440. .PP
  1441. \f5Define_Variable\fP creates a symbol with the given name
  1442. (using \f5Intern()\fP) and binds the symbol to the object
  1443. specified by the third argument.
  1444. The binding is established in the current lexical environment,
  1445. that is, in the same environment into which the bindings for
  1446. the module's primitive procedures are entered.
  1447. The first argument is a pointer to an \f5Object\fP that can later
  1448. be used to retrieve the current value of the variable (the \f5Object\fP
  1449. does \f2not\fP hold the variable's value itself).
  1450. The value of a variable can be read and modified by applying
  1451. the macro \f5Val()\fP to the object that serves as the variable's
  1452. handle.
  1453. Thus, we can add declaration like
  1454. .PG
  1455. static Object V_Window_Error_Handler;
  1456. .PE
  1457. to
  1458. .S window.c
  1459. and insert the line
  1460. .PG
  1461. Define_Variable (&V_Window_Error_Handler, "window-error-handler", Null);
  1462. .PE
  1463. into the \f5init_window()\fP function.
  1464. The interface function could then be defined like this:
  1465. .PG
  1466. struct symbol errcodes\|[\|] = {
  1467.     "out-of-windows",    EWindow,
  1468.     "bad-argument",    EBadArg,
  1469.     \f2[etc.]\fP
  1470.     0, 0
  1471. };
  1472.  ...
  1473. void Window_Error_Handler (int err) {
  1474.     Object args, func;
  1475.     GC_Node;
  1476. .sp .5
  1477.     args = C_To_Scheme_Symbol (err, errcodes);
  1478.     GC_Link (args);
  1479.     args = Cons (args, Null);
  1480.     GC_Unlink;
  1481.     func = Val (V_Window_Error_Handler);
  1482.     if (TYPE(func) == T_Compound)
  1483.     (void)Funcall (func, args, 0);
  1484.     Primitive_Error ("bad window error handler");
  1485.     /*NOTREACHED*/
  1486. }
  1487. .PE
  1488. The last step is to call
  1489. .PG
  1490. Error_Function = Window_Error_Handler;
  1491. .PE
  1492. from \f5init_window()\fP.
  1493. .QP
  1494. \s-1\f3Naming Convention:\fP Names of variables holding symbols
  1495. start with \f5Sym_\fP; variables holding Scheme variables
  1496. have a \f5V_\fP prefix.\s0
  1497. .sp .5
  1498. .PP
  1499. The function \f5Window_Error_Handler()\fP essentially calls a
  1500. Scheme function; this is done by means of the function \f5Funcall()\fP.
  1501. The first argument to \f5Funcall()\fP must be either a primitive
  1502. procedure, a compound procedure, or a control-point (that is, an
  1503. \f5Object\fP of type \f5T_Primitive\fP, \f5T_Compound\fP, or
  1504. \f5T_Control_Point\fP, respectively).
  1505. The second argument is a list holding the arguments to the function
  1506. being called.
  1507. The third argument is a flag indicating whether the arguments must
  1508. be evaluated prior to calling the function.
  1509. \f5Funcall()\fP returns the value (an \f5Object\fP) returned by
  1510. the function it has called (or, in case it called a control-point,
  1511. it does not return at all).
  1512. Note that the return value of the user-supplied error handler in the
  1513. example above is ignored.
  1514. In fact, the error handler is expected to not return at all;
  1515. if it \f2does\fP return, the Scheme error function \f5Primitive_Error()\fP
  1516. is called which definitely does not return.
  1517. \f5Primitive_Error()\fP is also invoked when
  1518. .S window-error-handler
  1519. does not hold a compound procedure.
  1520. .PP
  1521. The function \f5Cons()\fP creates an \f5Object\fP of type \f5T_Pair\fP;
  1522. its arguments are the \f5Car\fP and the \f5Cdr\fP of the pair, respectively
  1523. (\f5Cons()\fP actually should have been called \f5Make_Pair()\fP).
  1524. Note that \f5args\fP must be GC-protected, since \f5Cons()\fP can
  1525. trigger a garbage collection.
  1526. \f5Funcall()\fP can cause a garbage collection, too, but this is
  1527. irrelevant in the example above, since no variables of type \f5Object\fP
  1528. are referenced after \f5Funcall()\fP has returned.
  1529. \f5C_To_Scheme_Symbol()\fP could be defined as the opposite
  1530. of \f5Scheme_To_C_Symbol()\fP; it receives as arguments an \f5int\fP
  1531. and a \f5struct symbol\fP array and returns the Scheme symbol
  1532. corresponding to the integer symbol (it must call \f5Intern()\fP
  1533. to create the symbol to be returned).
  1534. .LP
  1535. The error handling mechanism can now be used like this:
  1536. .SC
  1537. (set! window-error-handler          ; define a dumb error handler
  1538.   (lambda code
  1539.     (error 'window-error "~s" code)))
  1540. .PE
  1541. or
  1542. .SC
  1543. ;; dynamically redefine error handler
  1544. .sp .5
  1545. (call-with-current-continuation
  1546.   (lambda (return)
  1547.     (fluid-let
  1548.       ((window-error-handler
  1549.         (lambda (code)
  1550.       (display code)
  1551.       (return #f))))
  1552.       (make-window 0 0 400 600))))   ; expression returns #f or a window
  1553.  
  1554. .PE
  1555. .C Primitive Procedures With ``Stringable'' Arguments
  1556. .PP
  1557. Consider the window system function
  1558. .PG
  1559. void Set_Window_Title (Window w, char *title);
  1560. .PE
  1561. which allows the user of the window library to define a title for
  1562. a window that has been created by a call to \f5Window_Create()\fP.
  1563. The title could be displayed by the window system in a title bar
  1564. on top of the window frame.
  1565. The corresponding primitive procedure could then be used like
  1566. .SC
  1567. (set-window-title! w "Debugger window #1")
  1568. .PE
  1569. .\"or
  1570. .\".SC
  1571. .\"(set-window-title! w "\f6doghijkao\fP")
  1572. .\".PE
  1573. or
  1574. .SC
  1575. (set-window-title! w '/etc/passwd)
  1576. .PE
  1577. .PP
  1578. Note that
  1579. .S set-window-title!
  1580. can be called with a string as well as with a symbol.
  1581. The primitive procedure could be defined as
  1582. .PG
  1583. Object P_Set_Window_Title (Object w, Object title) {
  1584.     int n;
  1585.     char *s;
  1586. .sp .5
  1587.     Check_Type (w, T_Window);
  1588.     if (TYPE(title) == T_Symbol)
  1589.     title = SYMBOL(title)\->name;
  1590.     else if (TYPE(title) != T_String)
  1591.     Wrong_Type_Combination (title, "string or symbol");
  1592.     n = STRING(title)\->size;
  1593.     s = alloca (n+1);
  1594.     bcopy (STRING(title)\->data, s, n);
  1595.     s[n] = '\\0';
  1596.     Set_Window_Title (WINDOW(w)\->handle, s);
  1597.     return Void;
  1598. }
  1599. .PE
  1600. .PP
  1601. The procedure first checks whether the \f5title\fP argument is
  1602. a symbol and, if this is true, sets the argument to the name of
  1603. the symbol.
  1604. The \f5name\fP component of a symbol is an \f5Object\fP of type
  1605. \f5T_String\fP.
  1606. If \f5title\fP is neither a symbol nor a string, the error
  1607. function \f5Wrong_Type_Combination()\fP is invoked with the offending
  1608. \f5Object\fP and a string indicating the expected argument types.
  1609. \f5Wrong_Type_Combination()\fP invokes the Scheme error handler
  1610. with a message like \f5``set-window-title!: wrong argument type
  1611. integer (expected string or symbol)''\fP.
  1612. This function is typically used in cases where an argument can be
  1613. of one of several types or when one wants to use a ``C'' string
  1614. to describe the expected type.
  1615. Otherwise either \f5Check_Type()\fP is used, or arguments are
  1616. type-checked explicitly, and, if the type is wrong,
  1617. the function \f5Wrong_Type()\fP is called with the wrongly-typed
  1618. object and the expected type (one of the ``\f5T_\fP'' constants)
  1619. as arguments.
  1620. Note that \f5Check_Type()\fP is a macro, therefore a function call
  1621. is involved only in the error case.
  1622. .PP
  1623. The main work done by \f5P_Set_Window_Title()\fP is to convert
  1624. the Scheme string into a (null-terminated) ``C'' string.
  1625. Unfortunately, the contents of a Scheme string (the \f5data\fP
  1626. component) cannot directly be used as an argument to the
  1627. ``C'' function \f5Set_Window_Title()\fP, since the string is
  1628. not terminated by a null-byte.
  1629. Thus, the entire string has to be copied to a buffer that is one
  1630. byte larger than the length of the string, and a null-byte must
  1631. explicitly be appended.
  1632. Note that the buffer holding the ``C'' string is allocated on the
  1633. stack by a call to \f5alloca()\fP.
  1634. This has the advantage that the memory occupied by the buffer
  1635. need not be freed explicitly.
  1636. This is important, since primitive procedures do not necessarily
  1637. terminate normally.
  1638. For instance, an ``interrupt'' could occur
  1639. between the point the memory is allocated and the end of the
  1640. function, e.\|g. when the user presses the interrupt key;
  1641. the interrupt handler (as well as error handlers) never
  1642. returns to the point where the interrupt occurred.
  1643. The disadvantage is that typical implementations of \f5alloca()\fP
  1644. can cause the stack to overflow when called with a sufficiently
  1645. large size; thus, something like
  1646. .SC
  1647. (set-window-title! w (make-string 100000 #\ea))
  1648. .PE
  1649. would probably crash the interpreter or otherwise wreak havoc.
  1650. Note that \f5GC_Link()\fP and \f5GC_Unlink\fP are implemented
  1651. in a way so that an abnormal return from within a
  1652. \f5GC_Link\fP/\f5GC_Unlink\fP bracket does not cause any
  1653. inconsistencies.
  1654. .C Procedures With Keyword Arguments
  1655. .PP
  1656. In a ``real'' window system, the \f5Window_Create()\fP function
  1657. would probably have more arguments than just the location and
  1658. dimensions of the window to be created.
  1659. Suppose that it expects as additional arguments
  1660. the background color of the window, a flag indicating
  1661. whether a title-bar should be attached to the window, the width
  1662. of the window's border frame, the color of the border frame,
  1663. the handle of the ``parent window'' (assuming that there exists
  1664. a hierarchy among the active windows) or the constant \f5None\fP
  1665. if it has no parent, and a flag indicating whether the window
  1666. should be automatically raised to the top of all other windows
  1667. when the cursor enters a visible region of the window's surface.
  1668. Thus, the new \f5Window_Create()\fP is defined as
  1669. .PG
  1670. Window Window_Create (int x, int y, int width, int height, int background,
  1671.     Bool has_title, int border, int border_color, Window parent, Bool raise);
  1672. .PE
  1673. .PP
  1674. The corresponding primitive procedure can be modified accordingly
  1675. so that it accepts the new arguments.
  1676. Then one can write expressions like
  1677. .SC
  1678. (make-window 100 100 400 600 'white #f 2 'black 'none #t)
  1679. .PE
  1680. .PP
  1681. This is a loss.
  1682. How can one tell what an expression like this actually does without
  1683. either having memorized the calling interface or looking at the
  1684. function definition?
  1685. It would be much better if we were able to write something like
  1686. .SC
  1687. (make-window (x 100) (y 100) (width 400) (height 600) (background 'white)
  1688.   (border-color 'black) (border 2) (title #f) (raise #t) (parent 'none))
  1689. .PE
  1690. or
  1691. .SC
  1692. (make-window (width 400) (height 900) (x 0) (y 0))
  1693. .PE
  1694. .PP
  1695. Default values could be provided automatically for missing arguments.
  1696. Since keywords are used to identify the arguments, it would no
  1697. longer be necessary to specify the arguments in a pre-defined order.
  1698. Although it is feasible to modify \f5P_Make_Window()\fP so that it
  1699. accepts keyword arguments, one would not want to do this.
  1700. Note that the function must not evaluate its arguments; it does
  1701. not make sense to evaluate an expression like
  1702. .S "(x 100)" .
  1703. Thus, one would either have to declare the function as \f5NOEVAL\fP or
  1704. create a macro (which cannot be easily done from within ``C'').
  1705. .PP
  1706. On the other hand, in Scheme one could easily write a macro
  1707. that ``parses'' a list of keyword arguments and then calls the
  1708. simple
  1709. .S make-window
  1710. with the right number of arguments in the right order.
  1711. The macro could be defined like this:
  1712. .SC
  1713. (define-macro (better-make-window . args)
  1714.   (let ((x #f) (y #f) (width #f) (height #f) (background 'white)
  1715.         (border-color 'black) (border 2) (title #f) (raise #t)
  1716.         (parent 'none))
  1717.     (do ((a args (cdr a))) ((null? a))
  1718.       (cond
  1719.        ((not (and (pair? (car a)) (= (length (car a)) 2)))
  1720.         (error 'better-make-window "bad argument ~s" (car a)))
  1721.        ((memq (caar a) '(x y width height background border-color
  1722.                            border title raise parent))
  1723.         (eval `(set! ,(caar a) (cadar a))))
  1724.        (else
  1725.         (error 'better-make-window "unknown keyword: ~s" (car a)))))
  1726.     (if (not (and x y width height))
  1727.         (error 'better-make-window
  1728.                "you must specify location and size"))
  1729.     `(make-window ,x ,y ,width ,height ,background ,border-color
  1730.                   ,border ,title ,raise ,parent)))
  1731. .PE
  1732. .PP
  1733. Note that
  1734. .S x ,
  1735. .S y ,
  1736. .S width ,
  1737. and
  1738. .S height
  1739. are mandatory; default values are provided for the other arguments.
  1740. It should be clear that
  1741. .S better-make-window
  1742. could be improved, for instance, by mentioning the list of
  1743. permissible keywords only in one place (rather than in three
  1744. different places like in the definition above).
  1745. This could be done by placing all keywords into a list and use the
  1746. list in the call to
  1747. .S memq .
  1748. The actual arguments could also be stored in a list rather than
  1749. in individual variables, and this list could be ``spliced'' into
  1750. the call to
  1751. .S make-window
  1752. like so:
  1753. .SC
  1754. `(make-window ,@list-of-arguments)
  1755. .PE
  1756. .PP
  1757. Note that there is a naming conflict; if we called the macro
  1758. .S make-window ,
  1759. it would shadow the primitive procedure
  1760. .S make-window .
  1761. There are several ways to resolve the conflict;
  1762. for instance, the macro could be assigned a different name
  1763. (as has been done in the example above), or the primitive
  1764. procedures could be loaded into a separate environment
  1765. like this:
  1766. .SC
  1767. (define window-package
  1768.   (let ()
  1769.     (load 'window.o)
  1770.     (the-environment)))
  1771. .PE
  1772. and the call to
  1773. .S make-window
  1774. could then be changed to
  1775. .SC
  1776. `((access make-window window-package) ,x ,y ...)
  1777. .PE
  1778. where
  1779. .S access
  1780. is defined as
  1781. .SC
  1782. (define-macro (access sym env)
  1783.   `(eval ',sym ,env))
  1784. .PE
  1785. .SH
  1786. Other Argument Passing Techniques
  1787. .LP
  1788. [To be provided]
  1789. .bp
  1790. .C References
  1791. .LP
  1792. .nr PD .5v
  1793. .sp
  1794. .IP "1."
  1795. Jonathan Rees and William Clinger (editors):
  1796. .I
  1797. Revised\v'-.3m'\s-13\s0\v'.3m' Report on the Algorithmic
  1798. Language Scheme
  1799. .R
  1800. SIGPLAN Notices Vol. 21 Number 12 (December 1986).
  1801. .IP "2."
  1802. Bjarne Stroustrup:
  1803. .I
  1804. The C++ Programming Language
  1805. .R
  1806. Addison-Wesley, Reading, Massachusetts.  1986.
  1807. .IP "3."
  1808. Oliver Laumann:
  1809. .I
  1810. Reference Manual for the Elk Extension Language Interpreter
  1811. .R
  1812. .IP
  1813. .de PT
  1814. ..
  1815. .bp
  1816. .PX
  1817.